#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <stdio.h>

#define TEMP_LED        D2                          //宏定义温控灯
#define FAN             D3                          //宏定义风扇开关
#define SIREN           D4                          //宏定义警报器
#define SPRAY_SYSTEM    D5                          //宏定义喷雾系统

// 设置wifi接入信息(请根据个人的WiFi信息进行修改)
const char* ssid = "8404";
const char* password = "8404@gcp";
//const char* mqttServer = "192.168.0.121";
const char* mqttServer = "test.ranye-iot.net";
String sensorData;

//串口通信全局变量
char byte_recv = 0;
String data_recv = "";
int flag_recv_str = 0;
char *uart_recv_data;

//传感器变量
unsigned char temp, rh;
unsigned short soil_rh;

//设置默认温度及湿度阈值
unsigned int temp_max = 25, temp_min = 13;
unsigned int rh_max = 65, rh_min = 60;

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

void setup() {
  Serial.begin(115200);                             //初始化串口
  WiFi.mode(WIFI_STA);                              //设置ESP8266工作模式为无线终端模式
  connectWifi();                                    // 连接WiFi
  mqttClient.setServer(mqttServer, 1883);           // 设置MQTT服务器和端口号
  mqttClient.setCallback(receiveCallback);          // 设置MQTT订阅回调函数
  connectMQTTServer();                              // 连接MQTT服务器

  //初始化板载LED
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, LOW);

  //初始化温控灯IO
  pinMode(TEMP_LED, OUTPUT);
  digitalWrite(TEMP_LED, LOW);
  pinMode(FAN, OUTPUT);
  digitalWrite(FAN, LOW);
  pinMode(SIREN, OUTPUT);
  digitalWrite(SIREN, OUTPUT);
  pinMode(SPRAY_SYSTEM, OUTPUT);
  digitalWrite(SPRAY_SYSTEM, OUTPUT);
}

void loop() {
  if (mqttClient.connected()) {                     // 如果开发板成功连接服务器
      mqttClient.loop();                            // 保持心跳
    } else {                                        // 如果开发板未能成功连接服务器
      connectMQTTServer();                          // 则尝试连接服务器
    }
  
  //串口操作
  while (Serial.available() > 0) {
    byte_recv = Serial.read();

    flag_recv_str = 1;
    data_recv += byte_recv;
    delay(10);
  }

  if (1 == flag_recv_str) {
    flag_recv_str = 0;
    Serial.print("recv_str:");
    Serial.println(data_recv);
    data_recv = "";

    uart_recv_data = (char *)data_recv.c_str();

    if (uart_recv_data[3] == 1)                     //土壤湿度（数据格式请参考《1+X职业技能等级证书——传感网应用开发》）
    {
      soil_rh = (((unsigned short)uart_recv_data[5]) << 8) + uart_recv_data[6];
      sensorData = "{\"soil_rh\":" + String(soil_rh) + "}";//封装成json格式
      pubMQTTmsg(sensorData);                       //发布土壤湿度
    }
    else if (uart_recv_data[3] == 2)                //环境温湿度
    {
      temp = uart_recv_data[5];                     //温度
      rh = uart_recv_data[7];                       //湿度

      if(temp <= temp_min)                          //控制加热LED部分，小于temp_min则开灯加热
      {
        digitalWrite(TEMP_LED, HIGH);               //开灯补温
        digitalWrite(SIREN, LOW);                   //关闭警报
        digitalWrite(FAN, LOW);                     //关闭风扇通风
      }
      else if(temp >= (temp_max + temp_min) / 2 && temp <= temp_max)//达到中间值则关闭补温灯
      {
        digitalWrite(TEMP_LED, LOW);                //关灯恒温
        digitalWrite(SIREN, LOW);                   //关闭警报
        digitalWrite(FAN, LOW);                     //关闭风扇通风
      }
      else if(temp > temp_max)                      //温度过高则报警
      {
        digitalWrite(SIREN, HIGH);                  //打开高温警报
        digitalWrite(FAN, HIGH);                    //并打开风扇通风
      }
      Serial.printf("temp_max = %d\n", temp_max);

      if(rh <= rh_min)                              //湿度低出水
      {
        digitalWrite(SIREN, HIGH);                  //打开低湿度警报
        digitalWrite(SPRAY_SYSTEM, HIGH);           //打开喷雾
      }
      else if(rh >= (rh_max + rh_min) / 2)//达到中间值就停水
      {
        digitalWrite(SIREN, LOW);                   //关闭警报
        digitalWrite(SPRAY_SYSTEM, LOW);            //关闭喷雾
      }
      
      sensorData = "{\"temp\": " + String(temp) + ", \"rh\": " + String(rh) + "}";
      pubMQTTmsg(sensorData);                       //发布温湿度
    }
  }
}

// 订阅指定主题
void subscribeTopic(){
  String topicString = "GreenHouse-Pub-SetThreshold";
  char subTopic[topicString.length() + 1];  
  strcpy(subTopic, topicString.c_str());
  
  // 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
  if(mqttClient.subscribe(subTopic)){
    Serial.println("Subscrib Topic:");
    Serial.println(subTopic);
  } else {
    Serial.print("Subscribe Fail...");
  }  
}

void connectMQTTServer() {
  // 根据ESP8266的MAC地址生成客户端ID（避免与其它ESP8266的客户端ID重名）
  String clientId = "esp8266-" + WiFi.macAddress();

  // 连接MQTT服务器
  if (mqttClient.connect(clientId.c_str())) {
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    Serial.println("ClientId:");
    Serial.println(clientId);
    subscribeTopic();                               // 订阅指定主题
  } else {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}

// 发布信息
void pubMQTTmsg(String messageString) {
  //static int value; // 客户端发布信息用数字

  // 建立发布主题。
  String topicString = "GreenHouse-Pub-Data";       //大棚环境监测数据主题
  char publishTopic[topicString.length() + 1];
  strcpy(publishTopic, topicString.c_str());

  // 建立发布信息。信息内容以Hello World为起始，后面添加发布次数。
  //String messageString = "Hello World " + String(value++);
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());

  // 实现ESP8266向主题发布信息
  if (mqttClient.publish(publishTopic, publishMsg)) {
    Serial.println("Publish Topic:"); Serial.println(publishTopic);
    Serial.println("Publish message:"); Serial.println(publishMsg);
  } else {
    Serial.println("Message Publish Failed.");
  }
}

// ESP8266连接wifi
void connectWifi() {

  WiFi.begin(ssid, password);

  //等待WiFi连接,成功连接后输出成功信息
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected!");
  Serial.println("");
}

void receiveCallback(char* topic, byte* payload, unsigned int length) {

  Serial.print("Message Received [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println("");
  Serial.print("Message Length(Bytes) ");
  Serial.println(length);

  switch((char)payload[0])
  {
  //修改温度阈值
  case '$': sscanf((char *)payload, "$%d-%d", &temp_min, &temp_max);break;
  //修改湿度阈值
  case '#': sscanf((char *)payload, "#%d-%d", &rh_min, &rh_max);break;
  default: break;
  }
}
